use clap::{ArgAction, Parser, ValueEnum};
use tokio::sync::{mpsc, watch};
use tracing::{error, info};
use tracing_subscriber::{EnvFilter, FmtSubscriber, fmt::time::UtcTime};

use libp2p::core::Multiaddr;

use bc_manager::BcManagerHandler;

pub mod blockchain_logic;
pub mod debug_cmd;

mod mining;

#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
pub struct Args {
    /// Port to listen on
    #[arg(short = 'p', long, default_value_t = 3000)]
    web_port: u16,

    /// IP address to bind to
    #[arg(short = 'H', long, default_value_t = String::from("127.0.0.1"))]
    web_ip: String,

    /// Specify data directory
    #[arg(short, long)]
    database_dir: Option<String>,

    #[arg(short, long, default_value_t = false)]
    loadblockindextest: bool,

    #[arg(long, default_value_t = false)]
    printblocktree: bool,

    #[arg(long)]
    printblock: Option<String>,

    /// Generate coins
    #[arg(short, long)]
    gen_coins: Option<u32>,

    /// The log level for mini-blockchain
    #[arg(short = 'L', long, value_enum, default_value_t = LogLevel::Warn)]
    log_level: LogLevel,

    /// If log file of mini-blockchain is used when running with no ansi color
    #[arg(short = 'n', long = "no-ansi", action = ArgAction::SetTrue)]
    no_ansi: bool,

    #[clap(long)]
    p2p_listen_address: Option<Multiaddr>,

    #[clap(long)]
    secret_key_seed: Option<u8>,
}

impl Args {
    pub fn get_port(&self) -> u16 {
        self.web_port
    }

    pub fn get_bind(&self) -> String {
        self.web_ip.clone()
    }

    pub fn get_database_dir(&self) -> Option<String> {
        self.database_dir.clone()
    }

    pub fn get_p2p_listen_address(&self) -> Option<Multiaddr> {
        self.p2p_listen_address.clone()
        // match self.p2p_listen_address.clone() {
        //     Some(addr) => addr,
        //     None => Multiaddr::from_str("/ip4/0.0.0.0/tcp/0").unwrap(),
        // }
    }

    pub fn get_secret_key_seed(&self) -> Option<u8> {
        self.secret_key_seed
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
pub enum LogLevel {
    Error,
    Warn,
    Info,
    Debug,
    Trace,
}

impl LogLevel {
    pub fn to_tracing_level(&self) -> tracing::Level {
        match self {
            LogLevel::Error => tracing::Level::ERROR,
            LogLevel::Warn => tracing::Level::WARN,
            LogLevel::Info => tracing::Level::INFO,
            LogLevel::Debug => tracing::Level::DEBUG,
            LogLevel::Trace => tracing::Level::TRACE,
        }
    }
}

pub fn init_logger(args: &Args) {
    let level = args.log_level.to_tracing_level();
    let time_format = time::format_description::parse(
        "[year]-[month]-[day] [hour]:[minute]:[second].[subsecond digits:3]",
    )
    .expect("format string should be valid!");
    let timer = UtcTime::new(time_format);

    // 创建一个 `tracing` 订阅器，允许通过环境变量控制日志级别
    let filter = EnvFilter::new("info");
    let subscriber = FmtSubscriber::builder()
        .with_env_filter(filter)
        .with_max_level(level)
        .with_ansi(!args.no_ansi)
        .with_timer(timer)
        .finish();

    tracing::subscriber::set_global_default(subscriber).expect("Setting default subscriber failed");
}

pub fn dump_system_info() {
    println!("System info:");

    match sys_info::os_type() {
        Ok(os_type) => println!("\tOS Type: {}", os_type),
        Err(e) => error!("\tError getting OS type: {:?}", e),
    }

    match sys_info::os_release() {
        Ok(os_release) => println!("\tOS Release: {}", os_release),
        Err(e) => error!("\tError getting OS release: {:?}", e),
    }

    match sys_info::cpu_num() {
        Ok(cpu_num) => println!("\tCPU Cores: {} (the logical cpu quantity)", cpu_num),
        Err(e) => error!("\tError getting CPU cores: {:?}", e),
    }

    match sys_info::cpu_speed() {
        Ok(cpu_speed) => println!("\tCPU Speed: {} MHz", cpu_speed),
        Err(e) => error!("\tError getting CPU speed: {:?}", e),
    }

    match sys_info::mem_info() {
        Ok(mem_info) => {
            println!("\tMemory:");
            println!("\t\tTotal: {} MB", mem_info.total / 1024);
            println!("\t\tFree: {} MB", mem_info.free / 1024);
            println!("\t\tAvailable: {} MB", mem_info.avail / 1024);
        }
        Err(e) => error!("\tError getting memory info: {:?}", e),
    }

    // Notice, it just calculate current disk on Windows.
    // let disk_info = sys_info::disk_info().expect("Get disk info failed") ;
    // println!("\tDisk:\n\t\ttotal: {}\n\t\tfree: {}", disk_info.total, disk_info.free);
}

pub async fn start_web_server(
    args: &Args,
    bc_manager: BcManagerHandler,
    mut shutdown_signal: watch::Receiver<bool>,
) -> tokio::task::JoinHandle<()> {
    let addr = format!("{}:{}", args.get_bind(), args.get_port());

    // 启动前端（仅在开发模式下）
    // #[cfg(debug_assertions)]
    // let frontend_server = tokio::task::spawn_blocking(|| {
    //     let _ = tokio::process::Command::new("npm")
    //         .arg("run")
    //         .arg("dev")
    //         .arg("--host") // Add this flag
    //         .current_dir("../bc_explorer/frontend")
    //         .stdout(std::process::Stdio::inherit()) // Forward stdout
    //         .stderr(std::process::Stdio::inherit()) // Forward stderr
    //         .spawn();
    // });

    tokio::task::spawn(async move {
        let router = bc_explorer::create_router(bc_manager);
        let listener = tokio::net::TcpListener::bind(&addr)
            .await
            .expect("Failed to bind address");
        println!(
            "🚀 The server task of Blockchain Explorer running at http://{}",
            addr
        );
        bc_util::print_task_and_thread_id("web server task");

        axum::serve(listener, router)
            .with_graceful_shutdown(async move {
                // let _ = shutdown_signal.await;
                // Wait until the shutdown signal becomes true.
                shutdown_signal.changed().await.ok();
                if *shutdown_signal.borrow() {
                    info!("Shutdown signal received for the server task of Blockchain Explorer.");
                }
            })
            .await
            .expect("Web Server error");

        println!("🛑 The server task of Blockchain Explorer stopped");
    })
}

// pub async fn start_mining_task(
//     _args: &Args,
//     bc_manager: BcManagerHandler,
//     command_receiver: mpsc::Receiver<bc_lib::mining::MiningCommand>,
//     transaction_receiver: mpsc::Receiver<bc_base::transaction::Transaction>,
//     shutdown_signal: watch::Receiver<bool>,
// ) -> tokio::task::JoinHandle<()> {
//     tokio::task::spawn_blocking(move || {
//         println!("🚀 Starting the mining task ...");
//         bc_util::print_task_and_thread_id("mining task");
//         let rt = tokio::runtime::Runtime::new().unwrap();
//         rt.block_on(async move {
//             crate::mining::mining_task(
//                 command_receiver,
//                 transaction_receiver,
//                 shutdown_signal,
//                 bc_manager,
//             )
//             .await;
//         });
//         println!("🛑 The mining task stopped.");
//     })
// }

// pub fn start_mining_task_in_dedicated_thread(
pub fn start_mining_task(
    _args: &Args,
    bc_manager: BcManagerHandler,
    command_receiver: mpsc::Receiver<bc_lib::mining::MiningCommand>,
    transaction_receiver: mpsc::Receiver<bc_base::transaction::Transaction>,
    shutdown_signal: watch::Receiver<bool>,
) -> std::thread::JoinHandle<()> {
    std::thread::spawn(move || {
        println!("🚀 Starting the mining task ...");
        bc_util::print_task_and_thread_id("mining task");

        let rt = tokio::runtime::Builder::new_current_thread()
            .enable_all()
            .build()
            .unwrap();

        rt.block_on(async move {
            crate::mining::mining_task(
                command_receiver,
                transaction_receiver,
                shutdown_signal,
                bc_manager,
            )
            .await;
        });

        println!("🛑 The mining task stopped.");
    })
}

pub fn get_database_root_dir(args: &Args) -> std::path::PathBuf {
    let t = args.get_database_dir();
    let database_dir = t.unwrap_or(bc_lib::DEFAULT_BC_DB_ROOT_DIR.to_string());

    std::path::PathBuf::from(database_dir)
}

// cleanup before exit
pub async fn cleanup_before_exit(bc_manager: BcManagerHandler) {
    println!("Cleaning up before exiting ...");
    let mng = bc_manager.lock().await;
    mng.flush_database().await;
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_args_default_values() {
        let args = Args::parse();
        assert_eq!(args.get_port(), 3000);
        assert_eq!(args.get_bind(), "127.0.0.1");
    }
}
